package ee.parameterConverter.impl;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Date;

import ee.metadataUtils.ClassUtils;
import ee.parameterConverter.ConversionFailedException;
import ee.parameterConverter.Converter;
import ee.parameterConverter.EmptyValueException;
import ee.parameterConverter.GenericStringConverter;

public class GenericStringConverterImpl implements GenericStringConverter {

	public Object convert(String value, Type type) throws EmptyValueException,
			ConversionFailedException {
		
		if (type instanceof Class)
		{
			if (value == null)
			{
				throw new EmptyValueException("The given value is null");
			}
			if (value.length() == 0)
			{
				throw new EmptyValueException("The given String value has no length");
			}
			
			Class<?> classType = (Class<?>) type;
			
			if (ClassUtils.isPrimitive(classType))
			{
				classType = ClassUtils.getWrapper(classType);
			}
			
			if (classType.isAssignableFrom(String.class))
			{
				return value;
			}
			
			if (classType.isArray() && char[].class.isAssignableFrom(classType))
			{
				return value.toCharArray();
			}
			
			if (Date.class.isAssignableFrom(classType))
			{
				return new Date(Long.parseLong(value));
			}
			
			try
			{
				//try a constructor
				Constructor<?> constructor = classType.getConstructor(String.class);
				return constructor.newInstance(value);
			} catch (NoSuchMethodException e)
			{
				//try with something else
			} catch (InvocationTargetException e)
			{
				//try with something else
			} catch (Exception e)
			{
				throw new ConversionFailedException("Conversion failed while calling the constructor of '" + classType + "'", value, e);
			}
			
			try
			{
				//Some classes have a valueOf method
				Method method = classType.getMethod("valueOf", String.class);

				//only call it if it is static and returns the correct type
				if (!classType.isAssignableFrom(method.getReturnType()))
				{
					throw new ConversionFailedException("The return type of the valueOf(" + classType + ") method is not compatible with '" + type.getClass().getName() + "'", value);
				} else if (!Modifier.isStatic(method.getModifiers()))
				{
					throw new NoSuchMethodException("No static valueOf method found");
				} else
				{
					return method.invoke(null, value);
				}
			} catch (ConversionFailedException e)
			{
				throw e;
			} catch (NoSuchMethodException e)
			{
				throw new ConversionFailedException("Could not find a way to convert '" + value + "' (" + value.getClass() + ") to '" + classType + "'", value);
			} catch (InvocationTargetException e)
			{
				Throwable cause = e.getCause();
				if (cause == null)
				{
					cause = e;
				}
				
				throw new ConversionFailedException("Conversion failed while calling the valueOf method of '" + classType + "' with value '" + value + "'", value, cause);
				
			} catch (Exception e)
			{
				throw new ConversionFailedException("Conversion failed while calling the valueOf method of '" + classType + "' with value '" + value + "'", value, e);
			}
		} else
		{
			throw new ConversionFailedException("Can only convert to Class<?> types, given type: " + type + " (" + type.getClass().getName() + ")", value);
		}
	}
	
	public Object convert(String[] value, Type type) throws EmptyValueException,
	ConversionFailedException {
		
		if (value == null)
		{
			throw new EmptyValueException("The given value is null");
		}
		
		switch (value.length)
		{
			case 0:
			{
				throw new EmptyValueException("The given array value has no length");
			}
			case 1:
			{
				String stringValue = value[0];
				return convert(stringValue, type);
			}
			default:
			{
				throw new ConversionFailedException("Can not convert an array with more then 1 value to a simple type", value);
			}
		}
	}

	@Override
	public Converter<String, ?> getStringConverter(final Type type) {

		final GenericStringConverterImpl genericConverter = this;

		return new Converter<String, Object>() {

			@Override
			public Object convert(String value) throws EmptyValueException,
					ConversionFailedException {
				return genericConverter.convert(value, type);
			}

		};
	}
	
	@Override
	public Converter<String[], ?> getStringArrayConverter(final Type type) {
		
		final GenericStringConverterImpl genericConverter = this;
		
		return new Converter<String[], Object>() {
			
			@Override
			public Object convert(String[] value) throws EmptyValueException,
			ConversionFailedException {
				return genericConverter.convert(value, type);
			}
			
		};
	}
}
